// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/inspector/value-mirror.h"

#include <algorithm>
#include <cmath>

#include "src/debug/debug-interface.h"
#include "src/inspector/v8-debugger.h"
#include "src/inspector/v8-inspector-impl.h"
#include "src/inspector/v8-value-utils.h"

namespace v8_inspector {

using protocol::Response;
using protocol::Runtime::EntryPreview;
using protocol::Runtime::ObjectPreview;
using protocol::Runtime::PropertyPreview;
using protocol::Runtime::RemoteObject;

namespace {
    V8InspectorClient* clientFor(v8::Local<v8::Context> context)
    {
        return static_cast<V8InspectorImpl*>(
            v8::debug::GetInspector(context->GetIsolate()))
            ->client();
    }

    V8InternalValueType v8InternalValueTypeFrom(v8::Local<v8::Context> context,
        v8::Local<v8::Value> value)
    {
        if (!value->IsObject())
            return V8InternalValueType::kNone;
        V8InspectorImpl* inspector = static_cast<V8InspectorImpl*>(
            v8::debug::GetInspector(context->GetIsolate()));
        int contextId = InspectedContext::contextId(context);
        InspectedContext* inspectedContext = inspector->getContext(contextId);
        if (!inspectedContext)
            return V8InternalValueType::kNone;
        return inspectedContext->getInternalType(value.As<v8::Object>());
    }

    Response toProtocolValue(v8::Local<v8::Context> context,
        v8::Local<v8::Value> value, int maxDepth,
        std::unique_ptr<protocol::Value>* result)
    {
        if (!maxDepth)
            return Response::Error("Object reference chain is too long");
        maxDepth--;

        if (value->IsNull() || value->IsUndefined()) {
            *result = protocol::Value::null();
            return Response::OK();
        }
        if (value->IsBoolean()) {
            *result = protocol::FundamentalValue::create(value.As<v8::Boolean>()->Value());
            return Response::OK();
        }
        if (value->IsNumber()) {
            double doubleValue = value.As<v8::Number>()->Value();
            if (doubleValue >= std::numeric_limits<int>::min() && doubleValue <= std::numeric_limits<int>::max() && bit_cast<int64_t>(doubleValue) != bit_cast<int64_t>(-0.0)) {
                int intValue = static_cast<int>(doubleValue);
                if (intValue == doubleValue) {
                    *result = protocol::FundamentalValue::create(intValue);
                    return Response::OK();
                }
            }
            *result = protocol::FundamentalValue::create(doubleValue);
            return Response::OK();
        }
        if (value->IsString()) {
            *result = protocol::StringValue::create(
                toProtocolString(context->GetIsolate(), value.As<v8::String>()));
            return Response::OK();
        }
        if (value->IsArray()) {
            v8::Local<v8::Array> array = value.As<v8::Array>();
            std::unique_ptr<protocol::ListValue> inspectorArray = protocol::ListValue::create();
            uint32_t length = array->Length();
            for (uint32_t i = 0; i < length; i++) {
                v8::Local<v8::Value> value;
                if (!array->Get(context, i).ToLocal(&value))
                    return Response::InternalError();
                std::unique_ptr<protocol::Value> element;
                Response response = toProtocolValue(context, value, maxDepth, &element);
                if (!response.isSuccess())
                    return response;
                inspectorArray->pushValue(std::move(element));
            }
            *result = std::move(inspectorArray);
            return Response::OK();
        }
        if (value->IsObject()) {
            std::unique_ptr<protocol::DictionaryValue> jsonObject = protocol::DictionaryValue::create();
            v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(value);
            v8::Local<v8::Array> propertyNames;
            if (!object->GetPropertyNames(context).ToLocal(&propertyNames))
                return Response::InternalError();
            uint32_t length = propertyNames->Length();
            for (uint32_t i = 0; i < length; i++) {
                v8::Local<v8::Value> name;
                if (!propertyNames->Get(context, i).ToLocal(&name))
                    return Response::InternalError();
                // FIXME(yurys): v8::Object should support GetOwnPropertyNames
                if (name->IsString()) {
                    v8::Maybe<bool> hasRealNamedProperty = object->HasRealNamedProperty(
                        context, v8::Local<v8::String>::Cast(name));
                    if (hasRealNamedProperty.IsNothing() || !hasRealNamedProperty.FromJust())
                        continue;
                }
                v8::Local<v8::String> propertyName;
                if (!name->ToString(context).ToLocal(&propertyName))
                    continue;
                v8::Local<v8::Value> property;
                if (!object->Get(context, name).ToLocal(&property))
                    return Response::InternalError();
                if (property->IsUndefined())
                    continue;
                std::unique_ptr<protocol::Value> propertyValue;
                Response response = toProtocolValue(context, property, maxDepth, &propertyValue);
                if (!response.isSuccess())
                    return response;
                jsonObject->setValue(
                    toProtocolString(context->GetIsolate(), propertyName),
                    std::move(propertyValue));
            }
            *result = std::move(jsonObject);
            return Response::OK();
        }
        return Response::Error("Object couldn't be returned by value");
    }

    Response toProtocolValue(v8::Local<v8::Context> context,
        v8::Local<v8::Value> value,
        std::unique_ptr<protocol::Value>* result)
    {
        if (value->IsUndefined())
            return Response::OK();
        return toProtocolValue(context, value, 1000, result);
    }

    enum AbbreviateMode { kMiddle,
        kEnd };

    String16 abbreviateString(const String16& value, AbbreviateMode mode)
    {
        const size_t maxLength = 100;
        if (value.length() <= maxLength)
            return value;
        UChar ellipsis = static_cast<UChar>(0x2026);
        if (mode == kMiddle) {
            return String16::concat(
                value.substring(0, maxLength / 2), String16(&ellipsis, 1),
                value.substring(value.length() - maxLength / 2 + 1));
        }
        return String16::concat(value.substring(0, maxLength - 1), ellipsis);
    }

    String16 descriptionForSymbol(v8::Local<v8::Context> context,
        v8::Local<v8::Symbol> symbol)
    {
        return String16::concat(
            "Symbol(",
            toProtocolStringWithTypeCheck(context->GetIsolate(), symbol->Name()),
            ")");
    }

    String16 descriptionForBigInt(v8::Local<v8::Context> context,
        v8::Local<v8::BigInt> value)
    {
        v8::Isolate* isolate = context->GetIsolate();
        v8::TryCatch tryCatch(isolate);
        v8::Local<v8::String> description;
        if (!value->ToString(context).ToLocal(&description))
            return String16();
        return toProtocolString(isolate, description) + "n";
    }

    String16 descriptionForPrimitiveType(v8::Local<v8::Context> context,
        v8::Local<v8::Value> value)
    {
        if (value->IsUndefined())
            return RemoteObject::TypeEnum::Undefined;
        if (value->IsNull())
            return RemoteObject::SubtypeEnum::Null;
        if (value->IsBoolean()) {
            return value.As<v8::Boolean>()->Value() ? "true" : "false";
        }
        if (value->IsString()) {
            return toProtocolString(context->GetIsolate(), value.As<v8::String>());
        }
        UNREACHABLE();
        return String16();
    }

    String16 descriptionForRegExp(v8::Isolate* isolate,
        v8::Local<v8::RegExp> value)
    {
        String16Builder description;
        description.append('/');
        description.append(toProtocolString(isolate, value->GetSource()));
        description.append('/');
        v8::RegExp::Flags flags = value->GetFlags();
        if (flags & v8::RegExp::Flags::kGlobal)
            description.append('g');
        if (flags & v8::RegExp::Flags::kIgnoreCase)
            description.append('i');
        if (flags & v8::RegExp::Flags::kMultiline)
            description.append('m');
        if (flags & v8::RegExp::Flags::kDotAll)
            description.append('s');
        if (flags & v8::RegExp::Flags::kUnicode)
            description.append('u');
        if (flags & v8::RegExp::Flags::kSticky)
            description.append('y');
        return description.toString();
    }

    enum class ErrorType { kNative,
        kClient };

    String16 descriptionForError(v8::Local<v8::Context> context,
        v8::Local<v8::Object> object, ErrorType type)
    {
        v8::Isolate* isolate = context->GetIsolate();
        v8::TryCatch tryCatch(isolate);
        String16 className = toProtocolString(isolate, object->GetConstructorName());
        v8::Local<v8::Value> stackValue;
        if (!object->Get(context, toV8String(isolate, "stack"))
                 .ToLocal(&stackValue)
            || !stackValue->IsString()) {
            return className;
        }
        String16 stack = toProtocolString(isolate, stackValue.As<v8::String>());
        String16 description = stack;
        if (type == ErrorType::kClient) {
            if (stack.substring(0, className.length()) != className) {
                v8::Local<v8::Value> messageValue;
                if (!object->Get(context, toV8String(isolate, "message"))
                         .ToLocal(&messageValue)
                    || !messageValue->IsString()) {
                    return stack;
                }
                String16 message = toProtocolStringWithTypeCheck(isolate, messageValue);
                size_t index = stack.find(message);
                String16 stackWithoutMessage = index != String16::kNotFound
                    ? stack.substring(index + message.length())
                    : String16();
                description = className + ": " + message + stackWithoutMessage;
            }
        }
        return description;
    }

    String16 descriptionForObject(v8::Isolate* isolate,
        v8::Local<v8::Object> object)
    {
        return toProtocolString(isolate, object->GetConstructorName());
    }

    String16 descriptionForDate(v8::Local<v8::Context> context,
        v8::Local<v8::Date> date)
    {
        v8::Isolate* isolate = context->GetIsolate();
        v8::TryCatch tryCatch(isolate);
        v8::Local<v8::String> description;
        if (!date->ToString(context).ToLocal(&description)) {
            return descriptionForObject(isolate, date);
        }
        return toProtocolString(isolate, description);
    }

    String16 descriptionForScopeList(v8::Local<v8::Array> list)
    {
        return String16::concat(
            "Scopes[", String16::fromInteger(static_cast<size_t>(list->Length())),
            ']');
    }

    String16 descriptionForScope(v8::Local<v8::Context> context,
        v8::Local<v8::Object> object)
    {
        v8::Isolate* isolate = context->GetIsolate();
        v8::Local<v8::Value> value;
        if (!object->GetRealNamedProperty(context, toV8String(isolate, "description"))
                 .ToLocal(&value)) {
            return String16();
        }
        return toProtocolStringWithTypeCheck(isolate, value);
    }

    String16 descriptionForCollection(v8::Isolate* isolate,
        v8::Local<v8::Object> object, size_t length)
    {
        String16 className = toProtocolString(isolate, object->GetConstructorName());
        return String16::concat(className, '(', String16::fromInteger(length), ')');
    }

    String16 descriptionForEntry(v8::Local<v8::Context> context,
        v8::Local<v8::Object> object)
    {
        v8::Isolate* isolate = context->GetIsolate();
        String16 key;
        v8::Local<v8::Value> tmp;
        if (object->GetRealNamedProperty(context, toV8String(isolate, "key"))
                .ToLocal(&tmp)) {
            auto wrapper = ValueMirror::create(context, tmp);
            if (wrapper) {
                std::unique_ptr<ObjectPreview> preview;
                int limit = 5;
                wrapper->buildEntryPreview(context, &limit, &limit, &preview);
                if (preview) {
                    key = preview->getDescription(String16());
                    if (preview->getType() == RemoteObject::TypeEnum::String) {
                        key = String16::concat('\"', key, '\"');
                    }
                }
            }
        }

        String16 value;
        if (object->GetRealNamedProperty(context, toV8String(isolate, "value"))
                .ToLocal(&tmp)) {
            auto wrapper = ValueMirror::create(context, tmp);
            if (wrapper) {
                std::unique_ptr<ObjectPreview> preview;
                int limit = 5;
                wrapper->buildEntryPreview(context, &limit, &limit, &preview);
                if (preview) {
                    value = preview->getDescription(String16());
                    if (preview->getType() == RemoteObject::TypeEnum::String) {
                        value = String16::concat('\"', value, '\"');
                    }
                }
            }
        }

        return key.length() ? ("{" + key + " => " + value + "}") : value;
    }

    String16 descriptionForFunction(v8::Local<v8::Context> context,
        v8::Local<v8::Function> value)
    {
        v8::Isolate* isolate = context->GetIsolate();
        v8::TryCatch tryCatch(isolate);
        v8::Local<v8::String> description;
        if (!value->ToString(context).ToLocal(&description)) {
            return descriptionForObject(isolate, value);
        }
        return toProtocolString(isolate, description);
    }

    class PrimitiveValueMirror final : public ValueMirror {
    public:
        PrimitiveValueMirror(v8::Local<v8::Value> value, const String16& type)
            : m_value(value)
            , m_type(type)
        {
        }

        v8::Local<v8::Value> v8Value() const override { return m_value; }
        Response buildRemoteObject(
            v8::Local<v8::Context> context, WrapMode mode,
            std::unique_ptr<RemoteObject>* result) const override
        {
            std::unique_ptr<protocol::Value> protocolValue;
            toProtocolValue(context, m_value, &protocolValue);
            *result = RemoteObject::create()
                          .setType(m_type)
                          .setValue(std::move(protocolValue))
                          .build();
            if (m_value->IsNull())
                (*result)->setSubtype(RemoteObject::SubtypeEnum::Null);
            return Response::OK();
        }

        void buildEntryPreview(
            v8::Local<v8::Context> context, int* nameLimit, int* indexLimit,
            std::unique_ptr<ObjectPreview>* preview) const override
        {
            *preview = ObjectPreview::create()
                           .setType(m_type)
                           .setDescription(descriptionForPrimitiveType(context, m_value))
                           .setOverflow(false)
                           .setProperties(protocol::Array<PropertyPreview>::create())
                           .build();
            if (m_value->IsNull())
                (*preview)->setSubtype(RemoteObject::SubtypeEnum::Null);
        }

        void buildPropertyPreview(
            v8::Local<v8::Context> context, const String16& name,
            std::unique_ptr<PropertyPreview>* preview) const override
        {
            *preview = PropertyPreview::create()
                           .setName(name)
                           .setValue(abbreviateString(
                               descriptionForPrimitiveType(context, m_value), kMiddle))
                           .setType(m_type)
                           .build();
            if (m_value->IsNull())
                (*preview)->setSubtype(RemoteObject::SubtypeEnum::Null);
        }

    private:
        v8::Local<v8::Value> m_value;
        String16 m_type;
        String16 m_subtype;
    };

    class NumberMirror final : public ValueMirror {
    public:
        explicit NumberMirror(v8::Local<v8::Number> value)
            : m_value(value)
        {
        }
        v8::Local<v8::Value> v8Value() const override { return m_value; }

        Response buildRemoteObject(
            v8::Local<v8::Context> context, WrapMode mode,
            std::unique_ptr<RemoteObject>* result) const override
        {
            bool unserializable = false;
            String16 descriptionValue = description(&unserializable);
            *result = RemoteObject::create()
                          .setType(RemoteObject::TypeEnum::Number)
                          .setDescription(descriptionValue)
                          .build();
            if (unserializable) {
                (*result)->setUnserializableValue(descriptionValue);
            } else {
                (*result)->setValue(protocol::FundamentalValue::create(m_value->Value()));
            }
            return Response::OK();
        }
        void buildPropertyPreview(
            v8::Local<v8::Context> context, const String16& name,
            std::unique_ptr<PropertyPreview>* result) const override
        {
            bool unserializable = false;
            *result = PropertyPreview::create()
                          .setName(name)
                          .setType(RemoteObject::TypeEnum::Number)
                          .setValue(description(&unserializable))
                          .build();
        }
        void buildEntryPreview(
            v8::Local<v8::Context> context, int* nameLimit, int* indexLimit,
            std::unique_ptr<ObjectPreview>* preview) const override
        {
            bool unserializable = false;
            *preview = ObjectPreview::create()
                           .setType(RemoteObject::TypeEnum::Number)
                           .setDescription(description(&unserializable))
                           .setOverflow(false)
                           .setProperties(protocol::Array<PropertyPreview>::create())
                           .build();
        }

    private:
        String16 description(bool* unserializable) const
        {
            *unserializable = true;
            double rawValue = m_value->Value();
            if (/*std::*/isnan(rawValue))
                return "NaN";
            if (rawValue == 0.0 && /*std::*/signbit(rawValue))
                return "-0";
            if (/*std::*/isinf(rawValue)) {
                return /*std::*/signbit(rawValue) ? "-Infinity" : "Infinity";
            }
            *unserializable = false;
            return String16::fromDouble(rawValue);
        }

        v8::Local<v8::Number> m_value;
    };

    class BigIntMirror final : public ValueMirror {
    public:
        explicit BigIntMirror(v8::Local<v8::BigInt> value)
            : m_value(value)
        {
        }

        Response buildRemoteObject(
            v8::Local<v8::Context> context, WrapMode mode,
            std::unique_ptr<RemoteObject>* result) const override
        {
            String16 description = descriptionForBigInt(context, m_value);
            *result = RemoteObject::create()
                          .setType(RemoteObject::TypeEnum::Bigint)
                          .setUnserializableValue(description)
                          .setDescription(description)
                          .build();
            return Response::OK();
        }

        void buildPropertyPreview(v8::Local<v8::Context> context,
            const String16& name,
            std::unique_ptr<protocol::Runtime::PropertyPreview>*
                preview) const override
        {
            *preview = PropertyPreview::create()
                           .setName(name)
                           .setType(RemoteObject::TypeEnum::Bigint)
                           .setValue(abbreviateString(
                               descriptionForBigInt(context, m_value), kMiddle))
                           .build();
        }

        void buildEntryPreview(v8::Local<v8::Context> context, int* nameLimit,
            int* indexLimit,
            std::unique_ptr<protocol::Runtime::ObjectPreview>*
                preview) const override
        {
            *preview = ObjectPreview::create()
                           .setType(RemoteObject::TypeEnum::Bigint)
                           .setDescription(descriptionForBigInt(context, m_value))
                           .setOverflow(false)
                           .setProperties(protocol::Array<PropertyPreview>::create())
                           .build();
        }

        v8::Local<v8::Value> v8Value() const override { return m_value; }

    private:
        v8::Local<v8::BigInt> m_value;
    };

    class SymbolMirror final : public ValueMirror {
    public:
        explicit SymbolMirror(v8::Local<v8::Value> value)
            : m_symbol(value.As<v8::Symbol>())
        {
        }

        Response buildRemoteObject(
            v8::Local<v8::Context> context, WrapMode mode,
            std::unique_ptr<RemoteObject>* result) const override
        {
            if (mode == WrapMode::kForceValue) {
                return Response::Error("Object couldn't be returned by value");
            }
            *result = RemoteObject::create()
                          .setType(RemoteObject::TypeEnum::Symbol)
                          .setDescription(descriptionForSymbol(context, m_symbol))
                          .build();
            return Response::OK();
        }

        void buildPropertyPreview(v8::Local<v8::Context> context,
            const String16& name,
            std::unique_ptr<protocol::Runtime::PropertyPreview>*
                preview) const override
        {
            *preview = PropertyPreview::create()
                           .setName(name)
                           .setType(RemoteObject::TypeEnum::Symbol)
                           .setValue(abbreviateString(
                               descriptionForSymbol(context, m_symbol), kEnd))
                           .build();
        }

        v8::Local<v8::Value> v8Value() const override { return m_symbol; }

    private:
        v8::Local<v8::Symbol> m_symbol;
    };

    class LocationMirror final : public ValueMirror {
    public:
        static std::unique_ptr<LocationMirror> create(
            v8::Local<v8::Function> function)
        {
            return create(function, function->ScriptId(),
                function->GetScriptLineNumber(),
                function->GetScriptColumnNumber());
        }
        static std::unique_ptr<LocationMirror> createForGenerator(
            v8::Local<v8::Value> value)
        {
            v8::Local<v8::debug::GeneratorObject> generatorObject = v8::debug::GeneratorObject::Cast(value);
            if (!generatorObject->IsSuspended()) {
                return create(generatorObject->Function());
            }
            v8::Local<v8::debug::Script> script;
            if (!generatorObject->Script().ToLocal(&script))
                return nullptr;
            v8::debug::Location suspendedLocation = generatorObject->SuspendedLocation();
            return create(value, script->Id(), suspendedLocation.GetLineNumber(),
                suspendedLocation.GetColumnNumber());
        }

        Response buildRemoteObject(
            v8::Local<v8::Context> context, WrapMode mode,
            std::unique_ptr<RemoteObject>* result) const override
        {
            auto location = protocol::DictionaryValue::create();
            location->setString("scriptId", String16::fromInteger(m_scriptId));
            location->setInteger("lineNumber", m_lineNumber);
            location->setInteger("columnNumber", m_columnNumber);
            *result = RemoteObject::create()
                          .setType(RemoteObject::TypeEnum::Object)
                          .setSubtype("internal#location")
                          .setDescription("Object")
                          .setValue(std::move(location))
                          .build();
            return Response::OK();
        }
        v8::Local<v8::Value> v8Value() const override { return m_value; }

    private:
        static std::unique_ptr<LocationMirror> create(v8::Local<v8::Value> value,
            int scriptId, int lineNumber,
            int columnNumber)
        {
            if (scriptId == v8::UnboundScript::kNoScriptId)
                return nullptr;
            if (lineNumber == v8::Function::kLineOffsetNotFound || columnNumber == v8::Function::kLineOffsetNotFound) {
                return nullptr;
            }
            return std::unique_ptr<LocationMirror>(
                new LocationMirror(value, scriptId, lineNumber, columnNumber));
        }

        LocationMirror(v8::Local<v8::Value> value, int scriptId, int lineNumber,
            int columnNumber)
            : m_value(value)
            , m_scriptId(scriptId)
            , m_lineNumber(lineNumber)
            , m_columnNumber(columnNumber)
        {
        }

        v8::Local<v8::Value> m_value;
        int m_scriptId;
        int m_lineNumber;
        int m_columnNumber;
    };

    class FunctionMirror final : public ValueMirror {
    public:
        explicit FunctionMirror(v8::Local<v8::Value> value)
            : m_value(value.As<v8::Function>())
        {
        }

        v8::Local<v8::Value> v8Value() const override { return m_value; }

        Response buildRemoteObject(
            v8::Local<v8::Context> context, WrapMode mode,
            std::unique_ptr<RemoteObject>* result) const override
        {
            // TODO(alph): drop this functionality.
            if (mode == WrapMode::kForceValue) {
                std::unique_ptr<protocol::Value> protocolValue;
                Response response = toProtocolValue(context, m_value, &protocolValue);
                if (!response.isSuccess())
                    return response;
                *result = RemoteObject::create()
                              .setType(RemoteObject::TypeEnum::Function)
                              .setValue(std::move(protocolValue))
                              .build();
            } else {
                *result = RemoteObject::create()
                              .setType(RemoteObject::TypeEnum::Function)
                              .setClassName(toProtocolStringWithTypeCheck(
                                  context->GetIsolate(), m_value->GetConstructorName()))
                              .setDescription(descriptionForFunction(context, m_value))
                              .build();
            }
            return Response::OK();
        }

        void buildPropertyPreview(
            v8::Local<v8::Context> context, const String16& name,
            std::unique_ptr<PropertyPreview>* result) const override
        {
            *result = PropertyPreview::create()
                          .setName(name)
                          .setType(RemoteObject::TypeEnum::Function)
                          .setValue(String16())
                          .build();
        }
        void buildEntryPreview(
            v8::Local<v8::Context> context, int* nameLimit, int* indexLimit,
            std::unique_ptr<ObjectPreview>* preview) const override
        {
            *preview = ObjectPreview::create()
                           .setType(RemoteObject::TypeEnum::Function)
                           .setDescription(descriptionForFunction(context, m_value))
                           .setOverflow(false)
                           .setProperties(protocol::Array<PropertyPreview>::create())
                           .build();
        }

    private:
        v8::Local<v8::Function> m_value;
    };

    bool isArrayLike(v8::Local<v8::Context> context, v8::Local<v8::Value> value,
        size_t* length)
    {
        if (!value->IsObject())
            return false;
        v8::Isolate* isolate = context->GetIsolate();
        v8::TryCatch tryCatch(isolate);
        v8::MicrotasksScope microtasksScope(isolate,
            v8::MicrotasksScope::kDoNotRunMicrotasks);
        v8::Local<v8::Object> object = value.As<v8::Object>();
        v8::Local<v8::Value> spliceValue;
        if (!object->IsArgumentsObject() && (!object->GetRealNamedProperty(context, toV8String(isolate, "splice")).ToLocal(&spliceValue) || !spliceValue->IsFunction())) {
            return false;
        }
        v8::Local<v8::Value> lengthValue;
        v8::Maybe<bool> result = object->HasOwnProperty(context, toV8String(isolate, "length"));
        if (result.IsNothing())
            return false;
        if (!result.FromJust() || !object->Get(context, toV8String(isolate, "length")).ToLocal(&lengthValue) || !lengthValue->IsUint32()) {
            return false;
        }
        *length = v8::Local<v8::Uint32>::Cast(lengthValue)->Value();
        return true;
    }

    struct EntryMirror {
        std::unique_ptr<ValueMirror> key;
        std::unique_ptr<ValueMirror> value;

        static bool getEntries(v8::Local<v8::Context> context,
            v8::Local<v8::Object> object, size_t limit,
            bool* overflow, std::vector<EntryMirror>* mirrors)
        {
            bool isKeyValue = false;
            v8::Local<v8::Array> entries;
            if (!object->PreviewEntries(&isKeyValue).ToLocal(&entries))
                return false;
            for (uint32_t i = 0; i < entries->Length(); i += isKeyValue ? 2 : 1) {
                v8::Local<v8::Value> tmp;

                std::unique_ptr<ValueMirror> keyMirror;
                if (isKeyValue && entries->Get(context, i).ToLocal(&tmp)) {
                    keyMirror = ValueMirror::create(context, tmp);
                }
                std::unique_ptr<ValueMirror> valueMirror;
                if (entries->Get(context, isKeyValue ? i + 1 : i).ToLocal(&tmp)) {
                    valueMirror = ValueMirror::create(context, tmp);
                } else {
                    continue;
                }
                if (mirrors->size() == limit) {
                    *overflow = true;
                    return true;
                }
                mirrors->emplace_back(
                    EntryMirror { std::move(keyMirror), std::move(valueMirror) });
            }
            return mirrors->size() > 0;
        }
    };

    class PreviewPropertyAccumulator : public ValueMirror::PropertyAccumulator {
    public:
        PreviewPropertyAccumulator(const std::vector<String16>& blacklist,
            int skipIndex, int* nameLimit, int* indexLimit,
            bool* overflow,
            std::vector<PropertyMirror>* mirrors)
            : m_blacklist(blacklist)
            , m_skipIndex(skipIndex)
            , m_nameLimit(nameLimit)
            , m_indexLimit(indexLimit)
            , m_overflow(overflow)
            , m_mirrors(mirrors)
        {
        }

        bool Add(PropertyMirror mirror) override
        {
            if (mirror.exception)
                return true;
            if ((!mirror.getter || !mirror.getter->v8Value()->IsFunction()) && !mirror.value) {
                return true;
            }
            if (!mirror.isOwn)
                return true;
            if (std::find(m_blacklist.begin(), m_blacklist.end(), mirror.name) != m_blacklist.end()) {
                return true;
            }
            if (mirror.isIndex && m_skipIndex > 0) {
                --m_skipIndex;
                if (m_skipIndex > 0)
                    return true;
            }
            int* limit = mirror.isIndex ? m_indexLimit : m_nameLimit;
            if (!*limit) {
                *m_overflow = true;
                return false;
            }
            --*limit;
            m_mirrors->push_back(std::move(mirror));
            return true;
        }

    private:
        std::vector<String16> m_blacklist;
        int m_skipIndex;
        int* m_nameLimit;
        int* m_indexLimit;
        bool* m_overflow;
        std::vector<PropertyMirror>* m_mirrors;
    };

    bool getPropertiesForPreview(v8::Local<v8::Context> context,
        v8::Local<v8::Object> object, int* nameLimit,
        int* indexLimit, bool* overflow,
        std::vector<PropertyMirror>* properties)
    {
        std::vector<String16> blacklist;
        size_t length = 0;
        if (object->IsArray() || isArrayLike(context, object, &length) || object->IsStringObject()) {
            blacklist.push_back("length");
        } else {
            auto clientSubtype = clientFor(context)->valueSubtype(object);
            if (clientSubtype && toString16(clientSubtype->string()) == "array") {
                blacklist.push_back("length");
            }
        }
        if (object->IsArrayBuffer() || object->IsSharedArrayBuffer()) {
            blacklist.push_back("[[Int8Array]]");
            blacklist.push_back("[[Uint8Array]]");
            blacklist.push_back("[[Int16Array]]");
            blacklist.push_back("[[Int32Array]]");
        }
        int skipIndex = object->IsStringObject()
            ? object.As<v8::StringObject>()->ValueOf()->Length() + 1
            : -1;
        PreviewPropertyAccumulator accumulator(blacklist, skipIndex, nameLimit,
            indexLimit, overflow, properties);
        return ValueMirror::getProperties(context, object, false, false,
            &accumulator);
    }

    void getInternalPropertiesForPreview(
        v8::Local<v8::Context> context, v8::Local<v8::Object> object,
        int* nameLimit, bool* overflow,
        std::vector<InternalPropertyMirror>* properties)
    {
        std::vector<InternalPropertyMirror> mirrors;
        ValueMirror::getInternalProperties(context, object, &mirrors);
        std::vector<String16> whitelist;
        if (object->IsBooleanObject() || object->IsNumberObject() || object->IsStringObject() || object->IsSymbolObject() || object->IsBigIntObject()) {
            whitelist.emplace_back("[[PrimitiveValue]]");
        } else if (object->IsPromise()) {
            whitelist.emplace_back("[[PromiseStatus]]");
            whitelist.emplace_back("[[PromiseValue]]");
        } else if (object->IsGeneratorObject()) {
            whitelist.emplace_back("[[GeneratorStatus]]");
        }
        for (auto& mirror : mirrors) {
            if (std::find(whitelist.begin(), whitelist.end(), mirror.name) == whitelist.end()) {
                continue;
            }
            if (!*nameLimit) {
                *overflow = true;
                return;
            }
            --*nameLimit;
            properties->push_back(std::move(mirror));
        }
    }

    void getPrivatePropertiesForPreview(
        v8::Local<v8::Context> context, v8::Local<v8::Object> object,
        int* nameLimit, bool* overflow,
        protocol::Array<PropertyPreview>* privateProperties)
    {
        std::vector<PrivatePropertyMirror> mirrors = ValueMirror::getPrivateProperties(context, object);
        std::vector<String16> whitelist;
        for (auto& mirror : mirrors) {
            std::unique_ptr<PropertyPreview> propertyPreview;
            mirror.value->buildPropertyPreview(context, mirror.name, &propertyPreview);
            if (!propertyPreview)
                continue;
            if (!*nameLimit) {
                *overflow = true;
                return;
            }
            --*nameLimit;
            privateProperties->addItem(std::move(propertyPreview));
        }
    }

    class ObjectMirror final : public ValueMirror {
    public:
        ObjectMirror(v8::Local<v8::Value> value, const String16& description)
            : m_value(value.As<v8::Object>())
            , m_description(description)
            , m_hasSubtype(false)
        {
        }
        ObjectMirror(v8::Local<v8::Value> value, const String16& subtype,
            const String16& description)
            : m_value(value.As<v8::Object>())
            , m_description(description)
            , m_hasSubtype(true)
            , m_subtype(subtype)
        {
        }

        v8::Local<v8::Value> v8Value() const override { return m_value; }

        Response buildRemoteObject(
            v8::Local<v8::Context> context, WrapMode mode,
            std::unique_ptr<RemoteObject>* result) const override
        {
            if (mode == WrapMode::kForceValue) {
                std::unique_ptr<protocol::Value> protocolValue;
                Response response = toProtocolValue(context, m_value, &protocolValue);
                if (!response.isSuccess())
                    return response;
                *result = RemoteObject::create()
                              .setType(RemoteObject::TypeEnum::Object)
                              .setValue(std::move(protocolValue))
                              .build();
            } else {
                v8::Isolate* isolate = context->GetIsolate();
                *result = RemoteObject::create()
                              .setType(RemoteObject::TypeEnum::Object)
                              .setClassName(toProtocolString(
                                  isolate, m_value->GetConstructorName()))
                              .setDescription(m_description)
                              .build();
                if (m_hasSubtype)
                    (*result)->setSubtype(m_subtype);
                if (mode == WrapMode::kWithPreview) {
                    std::unique_ptr<ObjectPreview> previewValue;
                    int nameLimit = 5;
                    int indexLimit = 100;
                    buildObjectPreview(context, false, &nameLimit, &indexLimit,
                        &previewValue);
                    (*result)->setPreview(std::move(previewValue));
                }
            }
            return Response::OK();
        }

        void buildObjectPreview(
            v8::Local<v8::Context> context, bool generatePreviewForTable,
            int* nameLimit, int* indexLimit,
            std::unique_ptr<ObjectPreview>* result) const override
        {
            buildObjectPreviewInternal(context, false /* forEntry */,
                generatePreviewForTable, nameLimit, indexLimit,
                result);
        }

        void buildEntryPreview(
            v8::Local<v8::Context> context, int* nameLimit, int* indexLimit,
            std::unique_ptr<ObjectPreview>* result) const override
        {
            buildObjectPreviewInternal(context, true /* forEntry */,
                false /* generatePreviewForTable */, nameLimit,
                indexLimit, result);
        }

        void buildPropertyPreview(
            v8::Local<v8::Context> context, const String16& name,
            std::unique_ptr<PropertyPreview>* result) const override
        {
            *result = PropertyPreview::create()
                          .setName(name)
                          .setType(RemoteObject::TypeEnum::Object)
                          .setValue(abbreviateString(
                              m_description,
                              m_subtype == RemoteObject::SubtypeEnum::Regexp ? kMiddle
                                                                             : kEnd))
                          .build();
            if (m_hasSubtype)
                (*result)->setSubtype(m_subtype);
        }

    private:
        void buildObjectPreviewInternal(
            v8::Local<v8::Context> context, bool forEntry,
            bool generatePreviewForTable, int* nameLimit, int* indexLimit,
            std::unique_ptr<ObjectPreview>* result) const
        {
            std::unique_ptr<protocol::Array<PropertyPreview>> properties = protocol::Array<PropertyPreview>::create();
            std::unique_ptr<protocol::Array<EntryPreview>> entriesPreview;
            bool overflow = false;

            v8::Local<v8::Value> value = m_value;
            while (value->IsProxy())
                value = value.As<v8::Proxy>()->GetTarget();

            if (value->IsObject() && !value->IsProxy()) {
                v8::Local<v8::Object> objectForPreview = value.As<v8::Object>();
                std::vector<InternalPropertyMirror> internalProperties;
                getInternalPropertiesForPreview(context, objectForPreview, nameLimit,
                    &overflow, &internalProperties);
                for (size_t i = 0; i < internalProperties.size(); ++i) {
                    std::unique_ptr<PropertyPreview> propertyPreview;
                    internalProperties[i].value->buildPropertyPreview(
                        context, internalProperties[i].name, &propertyPreview);
                    if (propertyPreview) {
                        properties->addItem(std::move(propertyPreview));
                    }
                }

                getPrivatePropertiesForPreview(context, objectForPreview, nameLimit,
                    &overflow, properties.get());

                std::vector<PropertyMirror> mirrors;
                if (getPropertiesForPreview(context, objectForPreview, nameLimit,
                        indexLimit, &overflow, &mirrors)) {
                    for (size_t i = 0; i < mirrors.size(); ++i) {
                        std::unique_ptr<PropertyPreview> preview;
                        std::unique_ptr<ObjectPreview> valuePreview;
                        if (mirrors[i].value) {
                            mirrors[i].value->buildPropertyPreview(context, mirrors[i].name,
                                &preview);
                            if (generatePreviewForTable) {
                                int tableLimit = 1000;
                                mirrors[i].value->buildObjectPreview(context, false, &tableLimit,
                                    &tableLimit, &valuePreview);
                            }
                        } else {
                            preview = PropertyPreview::create()
                                          .setName(mirrors[i].name)
                                          .setType(PropertyPreview::TypeEnum::Accessor)
                                          .build();
                        }
                        if (valuePreview) {
                            preview->setValuePreview(std::move(valuePreview));
                        }
                        properties->addItem(std::move(preview));
                    }
                }

                std::vector<EntryMirror> entries;
                if (EntryMirror::getEntries(context, objectForPreview, 5, &overflow,
                        &entries)) {
                    if (forEntry) {
                        overflow = true;
                    } else {
                        entriesPreview = protocol::Array<EntryPreview>::create();
                        for (const auto& entry : entries) {
                            std::unique_ptr<ObjectPreview> valuePreview;
                            entry.value->buildEntryPreview(context, nameLimit, indexLimit,
                                &valuePreview);
                            if (!valuePreview)
                                continue;
                            std::unique_ptr<ObjectPreview> keyPreview;
                            if (entry.key) {
                                entry.key->buildEntryPreview(context, nameLimit, indexLimit,
                                    &keyPreview);
                                if (!keyPreview)
                                    continue;
                            }
                            std::unique_ptr<EntryPreview> entryPreview = EntryPreview::create()
                                                                             .setValue(std::move(valuePreview))
                                                                             .build();
                            if (keyPreview)
                                entryPreview->setKey(std::move(keyPreview));
                            entriesPreview->addItem(std::move(entryPreview));
                        }
                    }
                }
            }
            *result = ObjectPreview::create()
                          .setType(RemoteObject::TypeEnum::Object)
                          .setDescription(m_description)
                          .setOverflow(overflow)
                          .setProperties(std::move(properties))
                          .build();
            if (m_hasSubtype)
                (*result)->setSubtype(m_subtype);
            if (entriesPreview)
                (*result)->setEntries(std::move(entriesPreview));
        }

        v8::Local<v8::Object> m_value;
        String16 m_description;
        bool m_hasSubtype;
        String16 m_subtype;
    };

    void nativeGetterCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
    {
        v8::Local<v8::Object> data = info.Data().As<v8::Object>();
        v8::Isolate* isolate = info.GetIsolate();
        v8::Local<v8::Context> context = isolate->GetCurrentContext();
        v8::Local<v8::Value> name;
        if (!data->GetRealNamedProperty(context, toV8String(isolate, "name"))
                 .ToLocal(&name)) {
            return;
        }
        v8::Local<v8::Value> object;
        if (!data->GetRealNamedProperty(context, toV8String(isolate, "object"))
                 .ToLocal(&object)
            || !object->IsObject()) {
            return;
        }
        v8::Local<v8::Value> value;
        if (!object.As<v8::Object>()->Get(context, name).ToLocal(&value))
            return;
        info.GetReturnValue().Set(value);
    }

    std::unique_ptr<ValueMirror> createNativeGetter(v8::Local<v8::Context> context,
        v8::Local<v8::Value> object,
        v8::Local<v8::Name> name)
    {
        v8::Isolate* isolate = context->GetIsolate();
        v8::TryCatch tryCatch(isolate);

        v8::Local<v8::Object> data = v8::Object::New(isolate);
        if (data->Set(context, toV8String(isolate, "name"), name).IsNothing()) {
            return nullptr;
        }
        if (data->Set(context, toV8String(isolate, "object"), object).IsNothing()) {
            return nullptr;
        }

        v8::Local<v8::Function> function;
        if (!v8::Function::New(context, nativeGetterCallback, data, 0,
                v8::ConstructorBehavior::kThrow)
                 .ToLocal(&function)) {
            return nullptr;
        }
        return ValueMirror::create(context, function);
    }

    void nativeSetterCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
    {
        if (info.Length() < 1)
            return;
        v8::Local<v8::Object> data = info.Data().As<v8::Object>();
        v8::Isolate* isolate = info.GetIsolate();
        v8::Local<v8::Context> context = isolate->GetCurrentContext();
        v8::Local<v8::Value> name;
        if (!data->GetRealNamedProperty(context, toV8String(isolate, "name"))
                 .ToLocal(&name)) {
            return;
        }
        v8::Local<v8::Value> object;
        if (!data->GetRealNamedProperty(context, toV8String(isolate, "object"))
                 .ToLocal(&object)
            || !object->IsObject()) {
            return;
        }
        v8::Local<v8::Value> value;
        if (!object.As<v8::Object>()->Set(context, name, info[0]).IsNothing())
            return;
    }

    std::unique_ptr<ValueMirror> createNativeSetter(v8::Local<v8::Context> context,
        v8::Local<v8::Value> object,
        v8::Local<v8::Name> name)
    {
        v8::Isolate* isolate = context->GetIsolate();
        v8::TryCatch tryCatch(isolate);

        v8::Local<v8::Object> data = v8::Object::New(isolate);
        if (data->Set(context, toV8String(isolate, "name"), name).IsNothing()) {
            return nullptr;
        }
        if (data->Set(context, toV8String(isolate, "object"), object).IsNothing()) {
            return nullptr;
        }

        v8::Local<v8::Function> function;
        if (!v8::Function::New(context, nativeSetterCallback, data, 1,
                v8::ConstructorBehavior::kThrow)
                 .ToLocal(&function)) {
            return nullptr;
        }
        return ValueMirror::create(context, function);
    }

    bool doesAttributeHaveObservableSideEffectOnGet(v8::Local<v8::Context> context,
        v8::Local<v8::Object> object,
        v8::Local<v8::Name> name)
    {
        // TODO(dgozman): we should remove this, annotate more embedder properties as
        // side-effect free, and call all getters which do not produce side effects.
        if (!name->IsString())
            return false;
        v8::Isolate* isolate = context->GetIsolate();
        if (!name.As<v8::String>()->StringEquals(toV8String(isolate, "body"))) {
            return false;
        }

        v8::TryCatch tryCatch(isolate);
        v8::Local<v8::Value> request;
        if (context->Global()
                ->GetRealNamedProperty(context, toV8String(isolate, "Request"))
                .ToLocal(&request)) {
            if (request->IsObject() && object->InstanceOf(context, request.As<v8::Object>()).FromMaybe(false)) {
                return true;
            }
        }
        if (tryCatch.HasCaught())
            tryCatch.Reset();

        v8::Local<v8::Value> response;
        if (context->Global()
                ->GetRealNamedProperty(context, toV8String(isolate, "Response"))
                .ToLocal(&response)) {
            if (response->IsObject() && object->InstanceOf(context, response.As<v8::Object>()).FromMaybe(false)) {
                return true;
            }
        }
        return false;
    }
    template <typename ArrayView, typename ArrayBuffer>
    void addTypedArrayView(v8::Local<v8::Context> context,
        v8::Local<ArrayBuffer> buffer, size_t length,
        const char* name,
        ValueMirror::PropertyAccumulator* accumulator)
    {
        accumulator->Add(PropertyMirror {
            String16(name), false, false, false, true, false,
            ValueMirror::create(context, ArrayView::New(buffer, 0, length)), nullptr,
            nullptr, nullptr, nullptr });
    }

    template <typename ArrayBuffer>
    void addTypedArrayViews(v8::Local<v8::Context> context,
        v8::Local<ArrayBuffer> buffer,
        ValueMirror::PropertyAccumulator* accumulator)
    {
        // TODO(alph): these should be internal properties.
        size_t length = buffer->ByteLength();
        addTypedArrayView<v8::Int8Array>(context, buffer, length, "[[Int8Array]]",
            accumulator);
        addTypedArrayView<v8::Uint8Array>(context, buffer, length, "[[Uint8Array]]",
            accumulator);
        if (buffer->ByteLength() % 2 == 0) {
            addTypedArrayView<v8::Int16Array>(context, buffer, length / 2,
                "[[Int16Array]]", accumulator);
        }
        if (buffer->ByteLength() % 4 == 0) {
            addTypedArrayView<v8::Int32Array>(context, buffer, length / 4,
                "[[Int32Array]]", accumulator);
        }
    }
} // anonymous namespace

ValueMirror::~ValueMirror() = default;

// static
bool ValueMirror::getProperties(v8::Local<v8::Context> context,
    v8::Local<v8::Object> object,
    bool ownProperties, bool accessorPropertiesOnly,
    PropertyAccumulator* accumulator)
{
    v8::Isolate* isolate = context->GetIsolate();
    v8::TryCatch tryCatch(isolate);
    v8::Local<v8::Set> set = v8::Set::New(isolate);

    v8::MicrotasksScope microtasksScope(isolate,
        v8::MicrotasksScope::kDoNotRunMicrotasks);
    V8InternalValueType internalType = v8InternalValueTypeFrom(context, object);
    if (internalType == V8InternalValueType::kScope) {
        v8::Local<v8::Value> value;
        if (!object->Get(context, toV8String(isolate, "object")).ToLocal(&value) || !value->IsObject()) {
            return false;
        } else {
            object = value.As<v8::Object>();
        }
    }
    if (internalType == V8InternalValueType::kScopeList) {
        if (!set->Add(context, toV8String(isolate, "length")).ToLocal(&set)) {
            return false;
        }
    }
    bool shouldSkipProto = internalType == V8InternalValueType::kScopeList;

    bool formatAccessorsAsProperties = clientFor(context)->formatAccessorsAsProperties(object);

    if (object->IsArrayBuffer()) {
        addTypedArrayViews(context, object.As<v8::ArrayBuffer>(), accumulator);
    }
    if (object->IsSharedArrayBuffer()) {
        addTypedArrayViews(context, object.As<v8::SharedArrayBuffer>(),
            accumulator);
    }

    for (auto iterator = v8::debug::PropertyIterator::Create(object);
         !iterator->Done(); iterator->Advance()) {
        bool isOwn = iterator->is_own();
        if (!isOwn && ownProperties)
            break;
        v8::Local<v8::Name> v8Name = iterator->name();
        v8::Maybe<bool> result = set->Has(context, v8Name);
        if (result.IsNothing())
            return false;
        if (result.FromJust())
            continue;
        if (!set->Add(context, v8Name).ToLocal(&set))
            return false;

        String16 name;
        std::unique_ptr<ValueMirror> symbolMirror;
        if (v8Name->IsString()) {
            name = toProtocolString(isolate, v8Name.As<v8::String>());
        } else {
            v8::Local<v8::Symbol> symbol = v8Name.As<v8::Symbol>();
            name = descriptionForSymbol(context, symbol);
            symbolMirror = ValueMirror::create(context, symbol);
        }

        v8::PropertyAttribute attributes;
        std::unique_ptr<ValueMirror> valueMirror;
        std::unique_ptr<ValueMirror> getterMirror;
        std::unique_ptr<ValueMirror> setterMirror;
        std::unique_ptr<ValueMirror> exceptionMirror;
        bool writable = false;
        bool enumerable = false;
        bool configurable = false;

        bool isAccessorProperty = false;
        v8::TryCatch tryCatch(isolate);
        if (!iterator->attributes().To(&attributes)) {
            exceptionMirror = ValueMirror::create(context, tryCatch.Exception());
        } else {
            if (iterator->is_native_accessor()) {
                if (iterator->has_native_getter()) {
                    getterMirror = createNativeGetter(context, object, v8Name);
                }
                if (iterator->has_native_setter()) {
                    setterMirror = createNativeSetter(context, object, v8Name);
                }
                writable = !(attributes & v8::PropertyAttribute::ReadOnly);
                enumerable = !(attributes & v8::PropertyAttribute::DontEnum);
                configurable = !(attributes & v8::PropertyAttribute::DontDelete);
                isAccessorProperty = getterMirror || setterMirror;
            } else {
                v8::TryCatch tryCatch(isolate);
                v8::debug::PropertyDescriptor descriptor;
                if (!iterator->descriptor().To(&descriptor)) {
                    exceptionMirror = ValueMirror::create(context, tryCatch.Exception());
                } else {
                    writable = descriptor.has_writable ? descriptor.writable : false;
                    enumerable = descriptor.has_enumerable ? descriptor.enumerable : false;
                    configurable = descriptor.has_configurable ? descriptor.configurable : false;
                    if (!descriptor.value.IsEmpty()) {
                        valueMirror = ValueMirror::create(context, descriptor.value);
                    }
                    bool getterIsNativeFunction = false;
                    if (!descriptor.get.IsEmpty()) {
                        v8::Local<v8::Value> get = descriptor.get;
                        getterMirror = ValueMirror::create(context, get);
                        getterIsNativeFunction = get->IsFunction() && get.As<v8::Function>()->ScriptId() == v8::UnboundScript::kNoScriptId;
                    }
                    if (!descriptor.set.IsEmpty()) {
                        setterMirror = ValueMirror::create(context, descriptor.set);
                    }
                    isAccessorProperty = getterMirror || setterMirror;
                    bool isSymbolDescription = object->IsSymbol() && name == "description";
                    if (isSymbolDescription || (name != "__proto__" && getterIsNativeFunction && formatAccessorsAsProperties && !doesAttributeHaveObservableSideEffectOnGet(context, object, v8Name))) {
                        v8::TryCatch tryCatch(isolate);
                        v8::Local<v8::Value> value;
                        if (object->Get(context, v8Name).ToLocal(&value)) {
                            valueMirror = ValueMirror::create(context, value);
                            isOwn = true;
                            setterMirror = nullptr;
                            getterMirror = nullptr;
                        }
                    }
                }
            }
        }
        if (accessorPropertiesOnly && !isAccessorProperty)
            continue;
        auto mirror = PropertyMirror { name,
            writable,
            configurable,
            enumerable,
            isOwn,
            iterator->is_array_index(),
            std::move(valueMirror),
            std::move(getterMirror),
            std::move(setterMirror),
            std::move(symbolMirror),
            std::move(exceptionMirror) };
        if (!accumulator->Add(std::move(mirror)))
            return true;
    }
    if (!shouldSkipProto && ownProperties && !object->IsProxy() && !accessorPropertiesOnly) {
        v8::Local<v8::Value> prototype = object->GetPrototype();
        if (prototype->IsObject()) {
            accumulator->Add(PropertyMirror { String16("__proto__"), true, true, false,
                true, false,
                ValueMirror::create(context, prototype),
                nullptr, nullptr, nullptr, nullptr });
        }
    }
    return true;
}

// static
void ValueMirror::getInternalProperties(
    v8::Local<v8::Context> context, v8::Local<v8::Object> object,
    std::vector<InternalPropertyMirror>* mirrors)
{
    v8::Isolate* isolate = context->GetIsolate();
    v8::MicrotasksScope microtasksScope(isolate,
        v8::MicrotasksScope::kDoNotRunMicrotasks);
    v8::TryCatch tryCatch(isolate);
    if (object->IsFunction()) {
        v8::Local<v8::Function> function = object.As<v8::Function>();
        auto location = LocationMirror::create(function);
        if (location) {
            mirrors->emplace_back(InternalPropertyMirror {
                String16("[[FunctionLocation]]"), std::move(location) });
        }
        if (function->IsGeneratorFunction()) {
            mirrors->emplace_back(InternalPropertyMirror {
                String16("[[IsGenerator]]"),
                ValueMirror::create(context, v8::True(context->GetIsolate())) });
        }
    }
    if (object->IsGeneratorObject()) {
        auto location = LocationMirror::createForGenerator(object);
        if (location) {
            mirrors->emplace_back(InternalPropertyMirror {
                String16("[[GeneratorLocation]]"), std::move(location) });
        }
    }
    V8Debugger* debugger = static_cast<V8InspectorImpl*>(v8::debug::GetInspector(isolate))
                               ->debugger();
    v8::Local<v8::Array> properties;
    if (debugger->internalProperties(context, object).ToLocal(&properties)) {
        for (uint32_t i = 0; i < properties->Length(); i += 2) {
            v8::Local<v8::Value> name;
            if (!properties->Get(context, i).ToLocal(&name) || !name->IsString()) {
                tryCatch.Reset();
                continue;
            }
            v8::Local<v8::Value> value;
            if (!properties->Get(context, i + 1).ToLocal(&value)) {
                tryCatch.Reset();
                continue;
            }
            auto wrapper = ValueMirror::create(context, value);
            if (wrapper) {
                mirrors->emplace_back(InternalPropertyMirror {
                    toProtocolStringWithTypeCheck(context->GetIsolate(), name),
                    std::move(wrapper) });
            }
        }
    }
}

// static
std::vector<PrivatePropertyMirror> ValueMirror::getPrivateProperties(
    v8::Local<v8::Context> context, v8::Local<v8::Object> object)
{
    std::vector<PrivatePropertyMirror> mirrors;
    v8::Isolate* isolate = context->GetIsolate();
    v8::MicrotasksScope microtasksScope(isolate,
        v8::MicrotasksScope::kDoNotRunMicrotasks);
    v8::TryCatch tryCatch(isolate);
    v8::Local<v8::Array> privateProperties;

    if (!v8::debug::GetPrivateFields(context, object).ToLocal(&privateProperties))
        return mirrors;

    for (uint32_t i = 0; i < privateProperties->Length(); i += 2) {
        v8::Local<v8::Value> name;
        if (!privateProperties->Get(context, i).ToLocal(&name)) {
            tryCatch.Reset();
            continue;
        }

        // Weirdly, v8::Private is set to be a subclass of v8::Data and
        // not v8::Value, meaning, we first need to upcast to v8::Data
        // and then downcast to v8::Private. Changing the hierarchy is a
        // breaking change now. Not sure if that's possible.
        //
        // TODO(gsathya): Add an IsPrivate method to the v8::Private and
        // assert here.
        v8::Local<v8::Private> private_field = v8::Local<v8::Private>::Cast(name);
        v8::Local<v8::Value> private_name = private_field->Name();
        DCHECK(!private_name->IsUndefined());

        v8::Local<v8::Value> value;
        if (!privateProperties->Get(context, i + 1).ToLocal(&value)) {
            tryCatch.Reset();
            continue;
        }
        auto wrapper = ValueMirror::create(context, value);
        if (wrapper) {
            mirrors.emplace_back(PrivatePropertyMirror {
                toProtocolStringWithTypeCheck(context->GetIsolate(), private_name),
                std::move(wrapper) });
        }
    }

    return mirrors;
}

String16 descriptionForNode(v8::Local<v8::Context> context,
    v8::Local<v8::Value> value)
{
    if (!value->IsObject())
        return String16();
    v8::Local<v8::Object> object = value.As<v8::Object>();
    v8::Isolate* isolate = context->GetIsolate();
    v8::TryCatch tryCatch(isolate);
    v8::Local<v8::Value> nodeName;
    if (!object->Get(context, toV8String(isolate, "nodeName"))
             .ToLocal(&nodeName)) {
        return String16();
    }
    String16 description;
    v8::Local<v8::Function> toLowerCase = v8::debug::GetBuiltin(isolate, v8::debug::kStringToLowerCase);
    if (nodeName->IsString()) {
        if (!toLowerCase->Call(context, nodeName, 0, nullptr).ToLocal(&nodeName))
            return String16();
        if (nodeName->IsString()) {
            description = toProtocolString(isolate, nodeName.As<v8::String>());
        }
    }
    if (!description.length()) {
        v8::Local<v8::Value> value;
        if (!object->Get(context, toV8String(isolate, "constructor"))
                 .ToLocal(&value)
            || !value->IsObject()) {
            return String16();
        }
        if (!value.As<v8::Object>()
                 ->Get(context, toV8String(isolate, "name"))
                 .ToLocal(&value)
            || !value->IsString()) {
            return String16();
        }
        description = toProtocolString(isolate, value.As<v8::String>());
    }
    v8::Local<v8::Value> nodeType;
    if (!object->Get(context, toV8String(isolate, "nodeType"))
             .ToLocal(&nodeType)
        || !nodeType->IsInt32()) {
        return description;
    }
    if (nodeType.As<v8::Int32>()->Value() == 1) {
        v8::Local<v8::Value> idValue;
        if (!object->Get(context, toV8String(isolate, "id")).ToLocal(&idValue)) {
            return description;
        }
        if (idValue->IsString()) {
            String16 id = toProtocolString(isolate, idValue.As<v8::String>());
            if (id.length()) {
                description = String16::concat(description, '#', id);
            }
        }
        v8::Local<v8::Value> classNameValue;
        if (!object->Get(context, toV8String(isolate, "className"))
                 .ToLocal(&classNameValue)) {
            return description;
        }
        if (classNameValue->IsString() && classNameValue.As<v8::String>()->Length()) {
            String16 classes = toProtocolString(isolate, classNameValue.As<v8::String>());
            String16Builder output;
            bool previousIsDot = false;
            for (size_t i = 0; i < classes.length(); ++i) {
                if (classes[i] == ' ') {
                    if (!previousIsDot) {
                        output.append('.');
                        previousIsDot = true;
                    }
                } else {
                    output.append(classes[i]);
                    previousIsDot = classes[i] == '.';
                }
            }
            description = String16::concat(description, '.', output.toString());
        }
    } else if (nodeType.As<v8::Int32>()->Value() == 1) {
        return String16::concat("<!DOCTYPE ", description, '>');
    }
    return description;
}

std::unique_ptr<ValueMirror> clientMirror(v8::Local<v8::Context> context,
    v8::Local<v8::Value> value,
    const String16& subtype)
{
    // TODO(alph): description and length retrieval should move to embedder.
    if (subtype == "node") {
        return v8::base::make_unique<ObjectMirror>(
            value, subtype, descriptionForNode(context, value));
    }
    if (subtype == "error") {
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Error,
            descriptionForError(context, value.As<v8::Object>(),
                ErrorType::kClient));
    }
    if (subtype == "array" && value->IsObject()) {
        v8::Isolate* isolate = context->GetIsolate();
        v8::TryCatch tryCatch(isolate);
        v8::Local<v8::Object> object = value.As<v8::Object>();
        v8::Local<v8::Value> lengthValue;
        if (object->Get(context, toV8String(isolate, "length"))
                .ToLocal(&lengthValue)) {
            if (lengthValue->IsInt32()) {
                return v8::base::make_unique<ObjectMirror>(
                    value, RemoteObject::SubtypeEnum::Array,
                    descriptionForCollection(isolate, object,
                        lengthValue.As<v8::Int32>()->Value()));
            }
        }
    }
    return v8::base::make_unique<ObjectMirror>(
        value,
        descriptionForObject(context->GetIsolate(), value.As<v8::Object>()));
}

std::unique_ptr<ValueMirror> ValueMirror::create(v8::Local<v8::Context> context,
    v8::Local<v8::Value> value)
{
    if (value->IsNull()) {
        return v8::base::make_unique<PrimitiveValueMirror>(
            value, RemoteObject::TypeEnum::Object);
    }
    if (value->IsBoolean()) {
        return v8::base::make_unique<PrimitiveValueMirror>(
            value, RemoteObject::TypeEnum::Boolean);
    }
    if (value->IsNumber()) {
        return v8::base::make_unique<NumberMirror>(value.As<v8::Number>());
    }
    v8::Isolate* isolate = context->GetIsolate();
    if (value->IsString()) {
        return v8::base::make_unique<PrimitiveValueMirror>(
            value, RemoteObject::TypeEnum::String);
    }
    if (value->IsBigInt()) {
        return v8::base::make_unique<BigIntMirror>(value.As<v8::BigInt>());
    }
    if (value->IsSymbol()) {
        return v8::base::make_unique<SymbolMirror>(value.As<v8::Symbol>());
    }
    auto clientSubtype = (value->IsUndefined() || value->IsObject())
        ? clientFor(context)->valueSubtype(value)
        : nullptr;
    if (clientSubtype) {
        String16 subtype = toString16(clientSubtype->string());
        return clientMirror(context, value, subtype);
    }
    if (value->IsUndefined()) {
        return v8::base::make_unique<PrimitiveValueMirror>(
            value, RemoteObject::TypeEnum::Undefined);
    }
    if (value->IsRegExp()) {
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Regexp,
            descriptionForRegExp(isolate, value.As<v8::RegExp>()));
    }
    if (value->IsFunction()) {
        return v8::base::make_unique<FunctionMirror>(value);
    }
    if (value->IsProxy()) {
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Proxy, "Proxy");
    }
    if (value->IsDate()) {
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Date,
            descriptionForDate(context, value.As<v8::Date>()));
    }
    if (value->IsPromise()) {
        v8::Local<v8::Promise> promise = value.As<v8::Promise>();
        return v8::base::make_unique<ObjectMirror>(
            promise, RemoteObject::SubtypeEnum::Promise,
            descriptionForObject(isolate, promise));
    }
    if (value->IsNativeError()) {
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Error,
            descriptionForError(context, value.As<v8::Object>(),
                ErrorType::kNative));
    }
    if (value->IsMap()) {
        v8::Local<v8::Map> map = value.As<v8::Map>();
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Map,
            descriptionForCollection(isolate, map, map->Size()));
    }
    if (value->IsSet()) {
        v8::Local<v8::Set> set = value.As<v8::Set>();
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Set,
            descriptionForCollection(isolate, set, set->Size()));
    }
    if (value->IsWeakMap()) {
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Weakmap,
            descriptionForObject(isolate, value.As<v8::Object>()));
    }
    if (value->IsWeakSet()) {
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Weakset,
            descriptionForObject(isolate, value.As<v8::Object>()));
    }
    if (value->IsMapIterator() || value->IsSetIterator()) {
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Iterator,
            descriptionForObject(isolate, value.As<v8::Object>()));
    }
    if (value->IsGeneratorObject()) {
        v8::Local<v8::Object> object = value.As<v8::Object>();
        return v8::base::make_unique<ObjectMirror>(
            object, RemoteObject::SubtypeEnum::Generator,
            descriptionForObject(isolate, object));
    }
    if (value->IsTypedArray()) {
        v8::Local<v8::TypedArray> array = value.As<v8::TypedArray>();
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Typedarray,
            descriptionForCollection(isolate, array, array->Length()));
    }
    if (value->IsArrayBuffer()) {
        v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>();
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Arraybuffer,
            descriptionForCollection(isolate, buffer, buffer->ByteLength()));
    }
    if (value->IsSharedArrayBuffer()) {
        v8::Local<v8::SharedArrayBuffer> buffer = value.As<v8::SharedArrayBuffer>();
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Arraybuffer,
            descriptionForCollection(isolate, buffer, buffer->ByteLength()));
    }
    if (value->IsDataView()) {
        v8::Local<v8::DataView> view = value.As<v8::DataView>();
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Dataview,
            descriptionForCollection(isolate, view, view->ByteLength()));
    }
    V8InternalValueType internalType = v8InternalValueTypeFrom(context, v8::Local<v8::Object>::Cast(value));
    if (value->IsArray() && internalType == V8InternalValueType::kScopeList) {
        return v8::base::make_unique<ObjectMirror>(
            value, "internal#scopeList",
            descriptionForScopeList(value.As<v8::Array>()));
    }
    if (value->IsObject() && internalType == V8InternalValueType::kEntry) {
        return v8::base::make_unique<ObjectMirror>(
            value, "internal#entry",
            descriptionForEntry(context, value.As<v8::Object>()));
    }
    if (value->IsObject() && internalType == V8InternalValueType::kScope) {
        return v8::base::make_unique<ObjectMirror>(
            value, "internal#scope",
            descriptionForScope(context, value.As<v8::Object>()));
    }
    size_t length = 0;
    if (value->IsArray() || isArrayLike(context, value, &length)) {
        length = value->IsArray() ? value.As<v8::Array>()->Length() : length;
        return v8::base::make_unique<ObjectMirror>(
            value, RemoteObject::SubtypeEnum::Array,
            descriptionForCollection(isolate, value.As<v8::Object>(), length));
    }
    if (value->IsObject()) {
        return v8::base::make_unique<ObjectMirror>(
            value, descriptionForObject(isolate, value.As<v8::Object>()));
    }
    return nullptr;
}
} // namespace v8_inspector
